<?php

/*
 * This file is part of the symfony package.
 * (c) 2004-2007 Fabien Potencier <fabien.potencier@symfony-project.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * sfImageMagickAdapter provides a mechanism for creating thumbnail images.
 * @see http://www.imagemagick.org
 *
 * @package    sfThumbnailPlugin
 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
 * @author     Benjamin Meynell <bmeynell@colorado.edu>
 */
class sfImageMagickAdapter
{
    protected
    $sourceWidth,
    $sourceHeight,
    $sourceMime,
    $maxWidth,
    $maxHeight,
    $scale,
    $inflate,
    $quality,
    $source,
    $magickCommands;
    /**
     * Mime types this adapter supports
     */
    protected $imgTypes = array(
        'application/pdf',
        'application/postscript',
        'application/vnd.palm',
        'application/x-icb',
        'application/x-mif',
        'image/dcx',
        'image/g3fax',
        'image/gif',
        'image/jng',
        'image/jpeg',
        'image/pbm',
        'image/pcd',
        'image/pict',
        'image/pjpeg',
        'image/png',
        'image/ras',
        'image/sgi',
        'image/svg',
        'image/tga',
        'image/tiff',
        'image/vda',
        'image/vnd.wap.wbmp',
        'image/vst',
        'image/x-fits',
        'image/x-ms-bmp',
        'image/x-otb',
        'image/x-palm',
        'image/x-pcx',
        'image/x-pgm',
        'image/x-photoshop',
        'image/x-ppm',
        'image/x-ptiff',
        'image/x-viff',
        'image/x-win-bitmap',
        'image/x-xbitmap',
        'image/x-xv',
        'image/xpm',
        'image/xwd',
        'text/plain',
        'video/mng',
        'video/mpeg',
        'video/mpeg2',
    );
    /**
     * Imagemagick-specific Type to Mime type map
     */
    protected $mimeMap = array(
        'bmp' => 'image/bmp',
        'bmp2' => 'image/bmp',
        'bmp3' => 'image/bmp',
        'cur' => 'image/x-win-bitmap',
        'dcx' => 'image/dcx',
        'epdf' => 'application/pdf',
        'epi' => 'application/postscript',
        'eps' => 'application/postscript',
        'eps2' => 'application/postscript',
        'eps3' => 'application/postscript',
        'epsf' => 'application/postscript',
        'epsi' => 'application/postscript',
        'ept' => 'application/postscript',
        'ept2' => 'application/postscript',
        'ept3' => 'application/postscript',
        'fax' => 'image/g3fax',
        'fits' => 'image/x-fits',
        'g3' => 'image/g3fax',
        'gif' => 'image/gif',
        'gif87' => 'image/gif',
        'icb' => 'application/x-icb',
        'ico' => 'image/x-win-bitmap',
        'icon' => 'image/x-win-bitmap',
        'jng' => 'image/jng',
        'jpeg' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'm2v' => 'video/mpeg2',
        'miff' => 'application/x-mif',
        'mng' => 'video/mng',
        'mpeg' => 'video/mpeg',
        'mpg' => 'video/mpeg',
        'otb' => 'image/x-otb',
        'p7' => 'image/x-xv',
        'palm' => 'image/x-palm',
        'pbm' => 'image/pbm',
        'pcd' => 'image/pcd',
        'pcds' => 'image/pcd',
        'pcl' => 'application/pcl',
        'pct' => 'image/pict',
        'pcx' => 'image/x-pcx',
        'pdb' => 'application/vnd.palm',
        'pdf' => 'application/pdf',
        'pgm' => 'image/x-pgm',
        'picon' => 'image/xpm',
        'pict' => 'image/pict',
        'pjpeg' => 'image/pjpeg',
        'png' => 'image/png',
        'png24' => 'image/png',
        'png32' => 'image/png',
    );

    public function __construct($maxWidth, $maxHeight, $scale, $inflate, $quality, $options)
    {
        $this->magickCommands = array();
        $this->magickCommands['convert'] = isset($options['convert']) ? escapeshellcmd($options['convert']) : 'convert';
        $this->magickCommands['identify'] = isset($options['identify']) ? escapeshellcmd($options['identify']) : 'identify';

        exec($this->magickCommands['convert'], $stdout);
        if (strpos($stdout[0], 'ImageMagick') === false)
        {
            throw new Exception(sprintf("ImageMagick convert command not found"));
        }

        exec($this->magickCommands['identify'], $stdout);
        if (strpos($stdout[0], 'ImageMagick') === false)
        {
            throw new Exception(sprintf("ImageMagick identify command not found"));
        }

        $this->maxWidth = $maxWidth;
        $this->maxHeight = $maxHeight;
        $this->scale = $scale;
        $this->inflate = $inflate;
        $this->quality = $quality;
        $this->options = $options;
    }

    public function toString($thumbnail, $targetMime = null)
    {
        ob_start();
        $this->save($thumbnail, null, $targetMime);

        return ob_get_clean();
    }

    public function toResource()
    {
        throw new Exception('The ImageMagick adapter does not support the toResource method.');
    }

    public function loadFile($thumbnail, $image)
    {
        // try and use getimagesize()
        // on failure, use identify instead
        $imgData = @getimagesize($image);
        if (!$imgData)
        {
            exec($this->magickCommands['identify'] . ' ' . escapeshellarg($image), $stdout, $retval);
            if ($retval === 1)
            {
                throw new Exception('Image could not be identified.');
            }
            else
            {
                // get image data via identify
                list($img, $type, $dimen) = explode(' ', $stdout[0]);
                list($width, $height) = explode('x', $dimen);

                $this->sourceWidth = $width;
                $this->sourceHeight = $height;
                $this->sourceMime = $this->mimeMap[strtolower($type)];
            }
        }
        else
        {
            // use image data from getimagesize()
            $this->sourceWidth = $imgData[0];
            $this->sourceHeight = $imgData[1];
            $this->sourceMime = $imgData['mime'];
        }
        $this->image = $image;

        // open file resource
        $source = fopen($image, 'r');

        $this->source = $source;

        $thumbnail->initThumb($this->sourceWidth, $this->sourceHeight, $this->maxWidth, $this->maxHeight, $this->scale, $this->inflate);

        return true;
    }

    public function loadData($thumbnail, $image, $mime)
    {
        throw new Exception('This function is not yet implemented. Try a different adapter.');
    }

    public function save($thumbnail, $thumbDest, $targetMime = null)
    {
        $command = '';

        $width = $this->sourceWidth;
        $height = $this->sourceHeight;
        $x = $y = 0;
        switch (@$this->options['method'])
        {
            case "shave_all":
                $proportion['source'] = $width / $height;
                $proportion['thumb'] = $thumbnail->getThumbWidth() / $thumbnail->getThumbHeight();

                if ($proportion['source'] > 1 && $proportion['thumb'] < 1)
                {
                    $x = ($width - $height * $proportion['thumb']) / 2;
                }
                else
                {
                    if ($proportion['source'] > $proportion['thumb'])
                    {
                        $x = ($width - $height * $proportion['thumb']) / 2;
                    }
                    else
                    {
                        $y = ($height - $width / $proportion['thumb']) / 2;
                    }
                }

                $command = sprintf(" -shave %dx%d", $x, $y);
                break;

            case "shave_bottom":
                if ($width > $height)
                {
                    $x = ceil(($width - $height) / 2);
                    $width = $height;
                }
                elseif ($height > $width)
                {
                    $y = 0;
                    $height = $width;
                }

                if (is_null($thumbDest))
                {
                    $command = sprintf(
                        " -crop %dx%d+%d+%d %s '-' | %s", $width, $height, $x, $y, escapeshellarg($this->image), $this->magickCommands['convert']
                    );

                    $this->image = '-';
                }
                else
                {
                    $command = sprintf(
                        " -crop %dx%d+%d+%d %s %s && %s", $width, $height, $x, $y, escapeshellarg($this->image), escapeshellarg($thumbDest), $this->magickCommands['convert']
                    );

                    $this->image = $thumbDest;
                }

                break;
            case 'custom':
                $coords = $this->options['coords'];
                if (empty($coords))
                    break;

                $x = $coords['x1'];
                $y = $coords['y1'];
                $width = $coords['x2'] - $coords['x1'];
                $height = $coords['y2'] - $coords['y1'];

                if (is_null($thumbDest))
                {
                    $command = sprintf(
                        " -crop %dx%d+%d+%d %s '-' | %s", $width, $height, $x, $y, escapeshellarg($this->image), $this->magickCommands['convert']
                    );

                    $this->image = '-';
                }
                else
                {
                    $command = sprintf(
                        " -crop %dx%d+%d+%d %s %s", $width, $height, $x, $y, escapeshellarg($this->image), escapeshellarg($thumbDest), $this->magickCommands['convert']
                    );

                    $this->image = $thumbDest;
                }
                break;
        } // end switch

        if (@$this->options['method'] != "custom")
        {
            $command .= ' -thumbnail ';
            $command .= $thumbnail->getThumbWidth() . 'x' . $thumbnail->getThumbHeight();
        }

        // absolute sizing
        if (!$this->scale && @$this->options['method'] != "custom")
        {
            $command .= '!';
        }

        //make sure this only happens when we're not cropping the image
        if (@$this->options['method'] != "custom")
        {
            if ($this->quality && $targetMime == 'image/jpeg')
            {
                $command .= ' -quality ' . $this->quality . '% ';
            }
            
            // extract images such as pages from a pdf doc
            $extract = '';
            if (isset($this->options['extract']) && is_int($this->options['extract']))
            {
                if ($this->options['extract'] > 0)
                {
                    $this->options['extract']--;
                }
                $extract = '[' . escapeshellarg($this->options['extract']) . '] ';
            }

            $output = (is_null($thumbDest)) ? '-' : $thumbDest;
            $output = (($mime = array_search($targetMime, $this->mimeMap)) ? $mime . ':' : '') . $output;

            $cmd = $this->magickCommands['convert'] . ' ' . $command . ' ' . escapeshellarg($this->image) . $extract . ' ' . escapeshellarg($output);
        }
        else
        {
            $quality = "";
            if ($this->quality && $targetMime == 'image/jpeg')
            {
                $quality = ' -quality ' . $this->quality . '% ';
            }

            $cmd = $this->magickCommands['convert'] . ' ' . $quality . $command;
        }

        (is_null($thumbDest)) ? passthru($cmd) : exec($cmd);
    }

    public function freeSource()
    {
        if (is_resource($this->source))
        {
            fclose($this->source);
        }
    }

    public function freeThumb()
    {
        return true;
    }

    public function getSourceMime()
    {
        return $this->sourceMime;
    }

}
